#include "Lightmap.h"
#include "LibGL/OpenGL.h"
#include "Shader.h"
#include "Light.h"
#include "Cubemap.h"
#include "RenderUtils.h"
#include "float.h"
#include "Windows/GxDebug.h"
#include "Basic/StringBuilder.h"
#include "FloatTextureEncoding.h"
namespace FinalRendering
{
	namespace Engine
	{
		using namespace GxRendering;

		union IDEncode
		{
			int ID;
			struct
			{
				Byte R,G,B,A;
			};
		};

		int Lightmap::GetHeight()
		{
			return height;
		}

		int Lightmap::GetWidth()
		{
			return width;
		}

		void Lightmap::SetFilter(bool filter)
		{
			glBindTexture(GL_TEXTURE_2D, handle);
			if (filter)
			{
				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
			}
			else
			{
				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
			}

		}

		Lightmap::Lightmap(int _width, int _height, bool clear)
		{
			width = _width;
			height = _height;
			glGenTextures(1, &handle);
			glEnable(GL_TEXTURE_2D);
			glBindTexture(GL_TEXTURE_2D, handle);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
			glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
			glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_FALSE);
			glDisable(GL_TEXTURE_2D);
			//glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
			if (clear)
			{
				Array<float> pix;
				pix.SetSize(width*height*4);
				memset(pix.Buffer(),0, sizeof(float)*pix.Count());
				glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, width, height, 0, GL_RGBA, GL_FLOAT, pix.Buffer());
			}
			else
				glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, width, height, 0, GL_RGBA, GL_FLOAT, 0);
		}

		Lightmap::~Lightmap()
		{
			glDeleteTextures(1, &handle);
		}

		void Lightmap::SetSize(int nw, int nh)
		{
			width = nw;
			height = nh;
		}

		int Lightmap::GetArea()
		{
			return width * height;
		}

		UInt Lightmap::GetHandle()
		{
			return handle;
		}

		void Lightmap::Load(Stream * stream)
		{
			stream->ReadBuffer(&width, 1);
			stream->ReadBuffer(&height, 1);
			unsigned int * data = new unsigned int[width * height];
			Array<float, true> decode; 
			stream->ReadBuffer(data, width*height);
			glBindTexture(GL_TEXTURE_2D, handle);
			const float factor = 1.0f/255.0f/255.0f*10.0f;
			DecodeData(data, decode, width, height);
			delete [] data;
			for (int i=0; i<decode.Count(); i++)
				if (decode[i] < 0.0f)
					decode[i] = 0.0f;
			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB9_E5, width, height, 0, GL_RGB,  GL_FLOAT, decode.Buffer());
		}

		void Lightmap::Save(Stream * stream)
		{
			float * data = new float[width*height*3];
			glBindTexture(GL_TEXTURE_2D, handle);
			glGetTexImage(GL_TEXTURE_2D, 0, GL_RGB, GL_FLOAT, data);
			stream->WriteBuffer(&width, 1);
			stream->WriteBuffer(&height, 1);
			Array<unsigned int, true> encode;
			EncodeData(data, encode, width, height);
			stream->WriteBuffer(encode.Buffer(), width*height);
			delete [] data;
		};

		void Lightmap::CopyImage(Array<float> & buffer)
		{
			buffer.SetSize(width*height*3);
			glBindTexture(GL_TEXTURE_2D, handle);
			glGetTexImage(GL_TEXTURE_2D, 0, GL_RGB, GL_FLOAT, buffer.Buffer());
		}

		void Lightmap::SetImage(Array<float> & buffer)
		{
			glBindTexture(GL_TEXTURE_2D, handle);
			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, width, height, 0, GL_RGB, GL_FLOAT, buffer.Buffer());
		}

		LightmapSolution::LightmapSolution(Scene * _scene)
		{
			scene = _scene;
			ComputeRadiosity = false;
			RadiosityThreshold = 0.1f;
			PatchSize = 32.0f;
		}

		
		void SetLightmapFilter(Lightmap * lm, bool linear)
		{
			glBindTexture(GL_TEXTURE_2D, lm->GetHandle());
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, linear?GL_LINEAR:GL_NEAREST);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, linear?GL_LINEAR:GL_NEAREST);
		}
		
		void BlurLightmap(Lightmap * lm, Lightmap * diffuseMap)
		{
			Lightmap * target = new Lightmap(lm->GetWidth(), lm->GetHeight());

			RefPtr<Program> progFlood = new Program(L"Shaders/LightmapFlood.gxprog");
			RefPtr<FrameBufferObject> fbo = new FrameBufferObject();
			RefPtr<RenderBuffer> rb = new RenderBuffer(GL_DEPTH_COMPONENT, lm->GetWidth(), lm->GetHeight());
			fbo->AttachRenderBuffer(GL_DEPTH_ATTACHMENT, rb.operator ->());
			fbo->AttachTexture(GL_COLOR_ATTACHMENT0, target->GetHandle());
			SetLightmapFilter(lm, false);
			SetLightmapFilter(target, false);
			MultiTexture::ActiveTexture(GL_TEXTURE0);
			glEnable(GL_TEXTURE_2D);
			glBindTexture(GL_TEXTURE_2D,lm->GetHandle());
			MultiTexture::ActiveTexture(GL_TEXTURE1);
			glEnable(GL_TEXTURE_2D);
			glBindTexture(GL_TEXTURE_2D,diffuseMap->GetHandle());
			MultiTexture::ActiveTexture(GL_TEXTURE0);
			progFlood->Bind();
			progFlood->SetUniform(L"src", 0);
			progFlood->SetUniform(L"diffuse", 1);
			
			fbo->Bind();
			bool ready = fbo->IsReady();
			glPushAttrib(GL_VIEWPORT_BIT);
			glViewport(0,0,lm->GetWidth(), lm->GetHeight());
			glMatrixMode(GL_PROJECTION);
			glPushMatrix();
			glLoadIdentity();
			glOrtho(0, lm->GetWidth(), lm->GetHeight(), 0, -1.0, 1.0);
			glMatrixMode(GL_MODELVIEW);
			glPushMatrix();
			float dx = 1.0f/lm->GetWidth();
			float dy = 1.0f/lm->GetHeight();
			progFlood->SetUniform(L"dsizeX", dx);
			progFlood->SetUniform(L"dsizeY", dy);
			glClearColor(0.0f,0.0f,0.0f,1.0f);
			for (int i=0; i<2; i++)
			{
				glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
				glLoadIdentity();
				{
					glBegin(GL_QUADS);
						glTexCoord2i(0,1);
						glVertex2i(0,0);
						glTexCoord2i(0,0);
						glVertex2i(0, lm->GetHeight());
						glTexCoord2i(1,0);
						glVertex2i(lm->GetWidth(), lm->GetHeight());
						glTexCoord2i(1,1);
						glVertex2i(lm->GetWidth(), 0);
					glEnd();
				}
				glBindTexture(GL_TEXTURE_2D,0);
				fbo->AttachTexture(GL_COLOR_ATTACHMENT0, lm->GetHandle());
				glBindTexture(GL_TEXTURE_2D,target->GetHandle());
			}
			fbo->UnBind();
			progFlood->UnBind();
			glPopMatrix();
			glMatrixMode(GL_PROJECTION);
			glPopMatrix();
			glMatrixMode(GL_MODELVIEW);
			glPopAttrib();
			
			MultiTexture::ActiveTexture(GL_TEXTURE1);
			glDisable(GL_TEXTURE_2D);
			MultiTexture::ActiveTexture(GL_TEXTURE0);
			glDisable(GL_TEXTURE_2D);
			SetLightmapFilter(lm, true);
			delete target;
		}

		void LightmapSolution::ChangeProgress(int val, int max)
		{
			ProgressChanged.Invoke(val, max);
		}

		void LightmapSolution::ChangeStatus(String s1, String s2)
		{
			StatusChanged.Invoke(s1, s2);
		}

		void LightmapSolution::Generate()
		{
			for (int i=0; i<8; i++)
			{
				MultiTexture::ActiveTexture(GL_TEXTURE0 + i);
				glDisable(GL_TEXTURE_2D);
				glDisable(GL_TEXTURE_CUBE_MAP);
			}
			MultiTexture::ActiveTexture(GL_TEXTURE0);
			ChangeStatus(L"Generating direct illumination...", L"Initializing lightmaps...");
			ChangeProgress(0,100);
			for (int i=0; i<3; i++)
				lightmaps[i].SetSize(scene->Lightmaps.Count());
			for (int i=0; i<scene->Lightmaps.Count(); i++)
			{
				for (int j=0; j<3; j++)
				{
					Lightmap * lm = new Lightmap(scene->Lightmaps[i].pixWidth, scene->Lightmaps[i].pixHeight);
					lightmaps[j][i] = lm;
				}
			}
			GenerateDirectIllumination(lightmaps);
			if (ComputeRadiosity)
			{
				ChangeStatus(L"Generating radiosity...", L"Initializing...");
				ChangeProgress(0,100);
				GenerateRadiosity();
			}
			ChangeStatus(L"Blurring solution...", L"");
			ChangeProgress(0,100);
			int progVal = 0;
			GenerateDiffuseMaps(lightmaps[0]);
			for (int j=0; j<3; j++)
				for (int i=0; i<lightmaps[j].Count(); i++)
				{
					progVal ++;
					ChangeProgress(progVal, lightmaps[j].Count()*3);
					BlurLightmap(lightmaps[j][i].operator ->(), diffuseMaps[i].operator ->());
				}
			ChangeStatus(L"Packing lightmaps...", L"");
			Merge();
		}

		void LightmapSolution::DrawNode(BSP_Node & node)
		{
			for (int i=0; i<node.SurfaceCount; i++)
			{
				IDEncode ec;
				ec.ID = node.Surfaces[i].LightmapID;
				progLight->SetUniform(L"id", vec4((float)ec.R/255.0f, (float)ec.G/255.0f, (float)ec.B/255.0f, (float)ec.A/255.0f));
				Surface * surf = node.Surfaces + i;
				glBegin(GL_POLYGON);
				for (int z=0; z<surf->VertexCount; z++)
				{
					glVertex3fv((float *)&(scene->Vertices[surf->Vertices[z]].Position));
				}
				glEnd();
			}
		}

		void LightmapSolution::BindLightmap(int id)
		{
			MultiTexture::ActiveTexture(GL_TEXTURE3);
			glEnable(GL_TEXTURE_2D);
			glBindTexture(GL_TEXTURE_2D, lightmaps[0][id]->GetHandle());
			MultiTexture::ActiveTexture(GL_TEXTURE4);
			glEnable(GL_TEXTURE_2D);
			glBindTexture(GL_TEXTURE_2D, lightmaps[1][id]->GetHandle());
			MultiTexture::ActiveTexture(GL_TEXTURE5);
			glEnable(GL_TEXTURE_2D);
			glBindTexture(GL_TEXTURE_2D, lightmaps[2][id]->GetHandle());
		}

		void LightmapSolution::UnBindLightmap()
		{
			MultiTexture::ActiveTexture(GL_TEXTURE3);
			glDisable(GL_TEXTURE_2D);
			MultiTexture::ActiveTexture(GL_TEXTURE4);
			glDisable(GL_TEXTURE_2D);
			MultiTexture::ActiveTexture(GL_TEXTURE5);
			glDisable(GL_TEXTURE_2D);
		}

		Lightmap * LightmapSolution::GetLightmap(int id, int axis)
		{
			return lightmaps[axis][id].operator->();
		}

		int LightmapSolution::GetMapCount()
		{
			return lightmaps[0].Count();
		}

		struct LightmapSolutionHeader
		{
			static const int ID = 0x0FCD1B48;
			int Identifier;
			int LightmapCount;
		};

		class IllegalLightmapSolutionException
		{};

		void LightmapSolution::Load(const String & fileName)
		{
			FileStream s(fileName, gxfmRead);
			Load(&s);
		}

		void LightmapSolution::Save(const String & fileName)
		{
			FileStream s(fileName, gxfmWrite);
			Save(&s);
		}

		void LightmapSolution::Load(Stream * stream)
		{
			LightmapSolutionHeader h;
			stream->ReadBuffer(&h, 1);
			if (h.Identifier != LightmapSolutionHeader::ID)
				throw IllegalLightmapSolutionException();
			lightmaps[0].Clear();
			lightmaps[1].Clear();
			lightmaps[2].Clear();
			for (int i=0; i<h.LightmapCount; i++)
			{
				for (int j=0; j<3; j++)
				{
					RefPtr<Lightmap> lm = new Lightmap(0,0);
					lm->Load(stream);
					lightmaps[j].Add(lm);
				}
			}
		}

		void LightmapSolution::Save(Stream * stream)
		{
			LightmapSolutionHeader h;
			h.Identifier = LightmapSolutionHeader::ID;
			h.LightmapCount = lightmaps[0].Count();
			stream->WriteBuffer(&h, 1);
			for (int i=0; i<h.LightmapCount; i++)
			{
				for (int j=0; j<3; j++)
				{
					lightmaps[j][i]->Save(stream);
				}
			}
		}

		void LightmapSolution::RenderScene()
		{
			int leaf = -1;
			scene->SelectLeaf(curLightPos, leaf);
			if (leaf == -1)
				return;
			glEnable(GL_DEPTH_TEST);
			glDisable(GL_TEXTURE_2D);
			glDisable(GL_BLEND);
			progLight->Bind();
			progLight->BindFragDataLocation(L"fragResult", 0);
			for (int i=0; i<scene->BSP.Count(); i++)
			{
				if (scene->BSP[i].IsLeaf() && scene->BSP[i].PVS_ID != -1)
				{
					bool vis = scene->PVS->Get(scene->BSP[leaf].PVS_ID,
						scene->BSP[i].PVS_ID);
					if (vis)
					{
						DrawNode(scene->BSP[i]);
					}
				}
			}
		}

		int GetPixID(Lightmap * lm, int i, int j)
		{
			return (j*lm->GetWidth()+i)*3;
		}

		void CPU_FloodPixels(Lightmap * lm)
		{
			Array<float> buf;
			lm->CopyImage(buf);
			Array<float> buf2;
			buf2.SetSize(buf.Count());
			for (int i=1; i<lm->GetWidth()-1; i++)
			{
				for (int j=1; j<lm->GetHeight()-1; j++)
				{
					int pid = GetPixID(lm, i, j);
					
					if (buf[pid] == -1.0f)
					{
						int p;
						p = GetPixID(lm, i-1, j-1);
						if (buf[p] > 0.0f)
						{
							buf2[pid] = buf[p]; buf2[pid+1] = buf[p+1]; buf2[pid+2] = buf[p+2];
							continue;
						}
						p = GetPixID(lm, i, j-1);
						if (buf[p] > 0.0f)
						{
							buf2[pid] = buf[p]; buf2[pid+1] = buf[p+1]; buf2[pid+2] = buf[p+2];
							continue;
						}
						p = GetPixID(lm, i+1, j-1);
						if (buf[p] > 0.0f)
						{
							buf2[pid] = buf[p]; buf2[pid+1] = buf[p+1]; buf2[pid+2] = buf[p+2];
							continue;
						}

						p = GetPixID(lm, i-1, j+1);
						if (buf[p] > 0.0f)
						{
							buf2[pid] = buf[p]; buf2[pid+1] = buf[p+1]; buf2[pid+2] = buf[p+2];
							continue;
						}
						p = GetPixID(lm, i, j+1);
						if (buf[p] > 0.0f)
						{
							buf2[pid] = buf[p]; buf2[pid+1] = buf[p+1]; buf2[pid+2] = buf[p+2];
							continue;
						}
						p = GetPixID(lm, i+1, j+1);
						if (buf[p] > 0.0f)
						{
							buf2[pid] = buf[p]; buf2[pid+1] = buf[p+1]; buf2[pid+2] = buf[p+2];
							continue;
						}

						p = GetPixID(lm, i-1, j);
						if (buf[p] > 0.0f)
						{
							buf2[pid] = buf[p]; buf2[pid+1] = buf[p+1]; buf2[pid+2] = buf[p+2];
							continue;
						}
						p = GetPixID(lm, i+1, j);
						if (buf[p] > 0.0f)
						{
							buf2[pid] = buf[p]; buf2[pid+1] = buf[p+1]; buf2[pid+2] = buf[p+2];
							continue;
						}
						buf2[pid] = buf[pid]; buf2[pid+1] = buf[pid+1]; buf2[pid+2] = buf[pid+2];
					}
					else
					{
						buf2[pid] = buf[pid]; buf2[pid+1] = buf[pid+1]; buf2[pid+2] = buf[pid+2];
					}
				}
			}
			lm->SetImage(buf2);
		}

		void FloodPixels(Lightmap * lm)
		{
			CPU_FloodPixels(lm);
			/*CPU_FloodPixels(lm);
			CPU_FloodPixels(lm);
			CPU_FloodPixels(lm);*/
		}

		Lightmap * LightmapSolution::GetDiffuseMap(int id)
		{
			return diffuseMaps[id].operator->();
		}

		void LightmapSolution::GenerateDiffuseMaps(Array<RefPtr<Lightmap>> & lightmaps)
		{
			glPushAttrib(GL_ENABLE_BIT);
			glDisable(GL_BLEND);
			glDisable(GL_DEPTH_TEST);
			glDisable(GL_CULL_FACE);
			MultiTexture::ActiveTexture(GL_TEXTURE0);
			glEnable(GL_TEXTURE_2D);
			glColor4ub(255,255,255,255);
			Shader::UseProgramObjectARB(0);
			RefPtr<FrameBufferObject> fbo = new FrameBufferObject();
			diffuseMaps.SetCapacity(lightmaps.Count());
			diffuseMaps.Clear();
			for (int i=0; i<lightmaps.Count(); i++)
			{
				Lightmap * m = new Lightmap(lightmaps[i]->GetWidth(), lightmaps[i]->GetHeight());
				m->SetFilter(false);
				diffuseMaps.Add(m);
				RefPtr<RenderBuffer> rb = new RenderBuffer(GL_DEPTH_COMPONENT, m->GetWidth(), m->GetHeight());
				fbo->AttachRenderBuffer(GL_DEPTH_ATTACHMENT, rb.operator ->());
				fbo->AttachTexture(GL_COLOR_ATTACHMENT0, m->GetHandle());
				fbo->Bind();

				bool ready = fbo->IsReady();
				SetupView(m->GetWidth(), m->GetHeight(), 1.0f, 1.0f);
				glClearColor(0.0,0.0,0.0,0.0);
				glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
				glTranslatef(0.0f,1.0f,0.0f);
				glScalef(1.0f,-1.0f,0.0f);
				for (int j=0; j<scene->BSP.Count(); j++)
				{
					if (!scene->BSP[j].IsLeaf())
						continue;
					for (int k=0; k<scene->BSP[j].SurfaceCount; k++)
					{
						Surface & surf = scene->BSP[j].Surfaces[k];
						if (surf.LightmapID == i)
						{
							BindDiffuseTexture.Invoke(surf.MatID);
							glBegin(GL_POLYGON);
								for (int z=0; z<surf.VertexCount; z++)
								{
									glTexCoord2fv((float*)&(scene->Vertices[surf.Vertices[z]].TexCoord));
									glVertex2fv((float*)&(scene->Vertices[surf.Vertices[z]].LightmapCoord));
								}
							glEnd();
						}
					}
				}
				ResetView();
				fbo->UnBind();
			}
			glPopAttrib();
		}

		void LightmapSolution::BlendRadiosity(Array<RefPtr<Lightmap>> * lmRads)
		{
			for (int j=0; j<3; j++)
			{
				for (int i=0; i<lightmaps[j].Count(); i++)
				{
					BlurLightmap(lmRads[j][i].operator ->(), diffuseMaps[i].operator ->());
				}
			}
			glPushAttrib(GL_ENABLE_BIT);
			RefPtr<FrameBufferObject> fbo = new FrameBufferObject();
			Shader::UseProgramObjectARB(0);
			MultiTexture::ActiveTexture(GL_TEXTURE0);
			glEnable(GL_TEXTURE_2D);
			glEnable(GL_BLEND);
			glDisable(GL_DEPTH_TEST);
			glBlendFunc(GL_ONE,GL_ONE);
			glColor4ub(255,255,255,255);
			glDrawBuffer(GL_COLOR_ATTACHMENT0);
			int progVal = 0;
			for (int j=0; j<3; j++)
			{
				for (int i=0; i<lightmaps[j].Count(); i++)
				{
					RefPtr<RenderBuffer> rb = new RenderBuffer(GL_DEPTH_COMPONENT, 
						lightmaps[j][i]->GetWidth(), lightmaps[j][i]->GetHeight());
					fbo->AttachTexture(GL_COLOR_ATTACHMENT0, lightmaps[j][i]->GetHandle());
					fbo->AttachRenderBuffer(GL_DEPTH_ATTACHMENT, rb.operator->());
					fbo->Bind();
					SetupView(lightmaps[j][i]->GetWidth(), lightmaps[j][i]->GetHeight(),
						(float)lightmaps[j][i]->GetWidth(), (float)lightmaps[j][i]->GetHeight());
					glBindTexture(GL_TEXTURE_2D, lmRads[j][i]->GetHandle());
					int w = lightmaps[j][i]->GetWidth();
					int h = lightmaps[j][i]->GetHeight();
					glBegin(GL_QUADS);
						glTexCoord2i(0,1);
						glVertex2i(0,0);
						glTexCoord2i(0,0);
						glVertex2i(0,h);
						glTexCoord2i(1,0);
						glVertex2i(w,h);
						glTexCoord2i(1,1);
						glVertex2i(w,0);
					glEnd();
					ResetView();
					progVal ++;
					ChangeProgress(progVal, lightmaps[j].Count()*3);
				}
			}
			glPopAttrib();
		}

		void LightmapSolution::GenerateRadiosity()
		{
			Array<bool, true> lightmapVis;
			lightmapVis.SetSize(lightmaps[0].Count());
			// Load Programs
			RefPtr<Program> progScatter = new Program(L"Shaders/RadScatter.gxprog");
			RefPtr<FrameBufferObject> fbo = new FrameBufferObject();
			RefPtr<Cubemap> cube = new Cubemap(512, Cubemap::Color, GL_RGBA);
			// Initialize Radiosity and Residual Lightmaps
			Array<RefPtr<Lightmap>> lmRads[3], lmResiduals;
			// Use a lower resolution in radiosity lightmaps
			
			for (int i=0; i<lightmaps[0].Count(); i++)
			{
				Lightmap * lm;
				for (int z=0; z<3; z++)
				{
					lm = new Lightmap(
						max(2,(int)ceil(scene->Lightmaps[i].AxisX.GetLength()/PatchSize)),
						max(2,(int)ceil(scene->Lightmaps[i].AxisY.GetLength()/PatchSize)), true);
					lmRads[z].Add(lm);
				}
				lm = new Lightmap(lm->GetWidth(), lm->GetHeight());
				lm->SetFilter(false);
				lmResiduals.Add(lm);
				
			}
			GenerateDiffuseMaps(lmRads[0]);
			GenerateDirectIlluminationNonBump(lmResiduals);

			Array<float> lmPix;
			
			float InitMaxEnergy = 0.0f;
			float convergeRate;
			while (true)
			{
				// Choose Shooter
				float maxEnergy = 0.1f; // Minimum Residual Energy
				int maxID = -1;
				for (int i=0; i<lmResiduals.Count(); i++)
				{
					float curMax = 0.0f;
					lmResiduals[i]->CopyImage(lmPix);
					for (int j=0; j<lmPix.Count()/3; j++)
					{
						float lum = 0.0f;
						lum = lmPix[j*3] + lmPix[j*3+1] + lmPix[j*3+2];
						if (lum > curMax)
							curMax = lum;
					}
					if (curMax > maxEnergy)
					{
						maxEnergy = curMax;
						maxID = i;
					}
				}
				if (maxID == -1)
					break;
				if (maxEnergy > InitMaxEnergy)
					InitMaxEnergy = maxEnergy;
				else
				{
					convergeRate = maxEnergy/InitMaxEnergy;
					PassFinished.Invoke(convergeRate);
					ChangeStatus(L"Computing radiosity...", String(L"Converged: ") + String((1.0f-convergeRate)*100.0f, 1) + L"%");
					if (maxEnergy/InitMaxEnergy < RadiosityThreshold)
					{
						break;
					}
				}
				
				Lightmap & lm = *lmResiduals[maxID];
				float biasX = 0.5f/lm.GetWidth();
				float biasY = 0.5f/lm.GetHeight();
				float areaShooter = scene->Lightmaps[maxID].AxisX.GetLength() * biasX * 
							scene->Lightmaps[maxID].AxisY.GetLength() * biasY * 4;
				
				lm.CopyImage(lmPix);
				int lastLeafID = -1;
				int progMax = lm.GetWidth() * lm.GetHeight();
				int progVal = 0;
				for (int i=0; i<lm.GetWidth(); i++)
				{
					for (int j=0; j<lm.GetHeight(); j++)
					{
						progVal++;
						int pixID = (j*lm.GetWidth()+i)*3;
						vec3 shooterEnergy = vec3(lmPix[pixID], lmPix[pixID+1], lmPix[pixID+2]);
						if (shooterEnergy.x+shooterEnergy.y+shooterEnergy.z < maxEnergy * 0.01f)
							continue;

						vec3 coord = vec3((i/(float)(lm.GetWidth()) + biasX),
										  (j/(float)(lm.GetHeight()) + biasY),0.0f);
						vec3 pos = scene->Lightmaps[maxID].OriPos + 
							scene->Lightmaps[maxID].AxisX * coord.x +
							scene->Lightmaps[maxID].AxisY * coord.y;

						coord.y = 1.0f - coord.y;
						int leafID = -1;
						scene->SelectLeaf(pos, leafID);
						if (leafID != lastLeafID)
						{
							memset(lightmapVis.Buffer(), 0, sizeof(bool)*lightmapVis.Count());
							lastLeafID = leafID;
							for (int k=0; k<scene->BSP.Count(); k++)
							{
								if (scene->BSP[k].IsLeaf())
								{
									if (scene->PVS->Get(scene->BSP[k].PVS_ID,
										scene->BSP[leafID].PVS_ID))
									{
										for (int l=0; l<scene->BSP[k].SurfaceCount; l++)
											lightmapVis[scene->BSP[k].Surfaces[l].LightmapID] = true;
									}
								}
							}
						}
						// Render Visibility Map
						curLightPos = pos; // To inform RenderScene function which light is rendering.
						Event<> proc;
						proc.Bind(this, &LightmapSolution::RenderScene);
						cube->Render(&proc, curLightPos);
						
						vec3 normal = scene->Lightmaps[maxID].Normal;
				
						glDisable(GL_DEPTH_TEST);
						glDepthMask(GL_FALSE);
						MultiTexture::ActiveTexture(GL_TEXTURE1);
						glDisable(GL_TEXTURE_2D);
						glEnable(GL_TEXTURE_CUBE_MAP);
						cube->Bind();
					
						MultiTexture::ActiveTexture(GL_TEXTURE0);
						
						glEnable(GL_TEXTURE_2D);
						glBindTexture(GL_TEXTURE_2D,  diffuseMaps[maxID]->GetHandle());
						fbo->Bind();
						progScatter->Bind();
						progScatter->SetUniform(L"shooterDiffuseMap", 0);
						progScatter->SetUniform(L"visMap", 1);
						progScatter->SetUniform(L"shooterCoord", coord);
						progScatter->SetUniform(L"shooterPos", pos);
						progScatter->SetUniform(L"normEmitter", normal);
						progScatter->SetUniform(L"shooterEnergy", shooterEnergy);
						progScatter->SetUniform(L"areaEmitter", areaShooter);
						// Scattering
						for (int k=0; k<lightmapVis.Count(); k++)
						{
							
							if (k==maxID)
								continue;
							if (!lightmapVis[k])
								continue;
							// Render to both radiosity and residual lightmaps
							progScatter->SetUniform(L"oriPos", scene->Lightmaps[k].OriPos);
							progScatter->SetUniform(L"axisX", scene->Lightmaps[k].AxisX);
							progScatter->SetUniform(L"axisY", scene->Lightmaps[k].AxisY);
							progScatter->SetUniform(L"normReceiver", scene->Lightmaps[k].Normal);
							IDEncode ec;
							ec.ID = k;
							vec4 vid(ec.R/255.0f, ec.G/255.0f, ec.B/255.0f, ec.A/255.0f);
							progScatter->SetUniform(L"polyID", vid);
							float areaReceiver = scene->Lightmaps[k].AxisX.GetLength() * 
								(1.0f/lmResiduals[k]->GetWidth()) *
								scene->Lightmaps[k].AxisY.GetLength() *
								(1.0f/lmResiduals[k]->GetHeight());
							progScatter->SetUniform(L"areaReceiver", areaReceiver);

							RefPtr<RenderBuffer> rb = new RenderBuffer(GL_DEPTH_COMPONENT, lmRads[0][k]->GetWidth(),
								lmRads[0][k]->GetHeight());

							fbo->AttachTexture(GL_COLOR_ATTACHMENT0, lmResiduals[k]->GetHandle());
							fbo->AttachTexture(GL_COLOR_ATTACHMENT1, lmRads[0][k]->GetHandle());
							fbo->AttachTexture(GL_COLOR_ATTACHMENT2, lmRads[1][k]->GetHandle());
							fbo->AttachTexture(GL_COLOR_ATTACHMENT3, lmRads[2][k]->GetHandle());

							fbo->AttachRenderBuffer(GL_DEPTH_ATTACHMENT, rb.operator->());
							const GLenum drawBuffers[] = {GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2, 
														  GL_COLOR_ATTACHMENT3};
							OpenGL::DrawBuffers(4, drawBuffers);
							bool ready = fbo->IsReady();

							progScatter->BindFragDataLocation(L"residual", 0);
							progScatter->BindFragDataLocation(L"radiosity1", 1);
							progScatter->BindFragDataLocation(L"radiosity2", 2);
							progScatter->BindFragDataLocation(L"radiosity3", 3);

							glEnable(GL_BLEND);
							glBlendFunc(GL_ONE, GL_ONE);

							int w = lmResiduals[k]->GetWidth();
							int h = lmResiduals[k]->GetHeight();
							SetupView(w, h, (float)w, (float)h);
							glBegin(GL_QUADS);
								glTexCoord2i(0,1);
								glVertex2i(0,0);
								glTexCoord2i(0,0);
								glVertex2i(0,h);
								glTexCoord2i(1,0);
								glVertex2i(w,h);
								glTexCoord2i(1,1);
								glVertex2i(w,0);
							glEnd();
							ResetView();
							glDrawBuffer(GL_COLOR_ATTACHMENT0);
						}
					
						fbo->UnBind();
						progScatter->UnBind();
						
						glEnable(GL_DEPTH_TEST);
						glDepthMask(GL_TRUE);
						MultiTexture::ActiveTexture(GL_TEXTURE1);
						glDisable(GL_TEXTURE_2D);
						glDisable(GL_TEXTURE_CUBE_MAP);
						MultiTexture::ActiveTexture(GL_TEXTURE0);

						
						
					}
					ChangeProgress(progVal, progMax);
				}
				// Clear Shooter Residual
				fbo->AttachTexture(GL_COLOR_ATTACHMENT0, lmResiduals[maxID]->GetHandle());
				int zero = 0;
				fbo->AttachTexture(GL_COLOR_ATTACHMENT1, zero);
				fbo->AttachTexture(GL_COLOR_ATTACHMENT2, zero);
				fbo->AttachTexture(GL_COLOR_ATTACHMENT3, zero);
				RefPtr<RenderBuffer> rb = new RenderBuffer(GL_DEPTH_COMPONENT, lmResiduals[maxID]->GetWidth(),
					lmResiduals[maxID]->GetHeight());
				fbo->AttachRenderBuffer(GL_DEPTH_ATTACHMENT, rb.operator ->());
				fbo->Bind();
				glClearColor(0.0f,0.0f,0.0f,0.0f);
				glClear(GL_COLOR_BUFFER_BIT);
				fbo->UnBind();
				MultiTexture::ActiveTexture(GL_TEXTURE1);
				glDisable(GL_TEXTURE_CUBE_MAP);
				MultiTexture::ActiveTexture(GL_TEXTURE0);
			}
			ChangeStatus(L"Computing radiosity...", "Blending...");
			BlendRadiosity(lmRads);
		}

		class LightmapDirectIlluminationGenerator : Object
		{
		protected:
			RefPtr<Program> progPatch;
			Scene * scene;
			LightmapSolution * solution;
			virtual void CreateProgram() = 0;
			virtual int GetLightmapCount() = 0;
			virtual int GetLightmapWidth(int id) = 0;
			virtual int GetLightmapHeight(int id) = 0;
			virtual void BindFBO(FrameBufferObject * fbo, int j) = 0;
			virtual void BindFragDataLoc() = 0;
		public:
			LightmapDirectIlluminationGenerator(LightmapSolution * s)
				: scene(s->scene), solution(s)
			{}
			void GenerateIllum()
			{
				CreateProgram();
				Array<RefPtr<Light>> lights;
				for (int i=0; i<scene->Entities.Count(); i++)
				{
					if (wcscmp(scene->Entities[i]->GetDesc()->ClassName(),L"Light") == 0)
					{
						Light * light = Light::FromEntity(scene->Entities[i]);
						if (light)
							lights.Add(light);
					}
				}
				RefPtr<FrameBufferObject> fbo = new FrameBufferObject();
				RefPtr<Cubemap> cube = new Cubemap(2048, Cubemap::Color, GL_RGBA);
				Array<bool, true> lightmapVis;
				lightmapVis.SetSize(GetLightmapCount());
				for (int i=0; i<lights.Count(); i++)
				{
					solution->ChangeStatus(L"Computing direct illumination...", String(L"Processing light ") + String(i+1) +
						L"/" + String(lights.Count()));
					// For each light, render a visibility cubemap at light position.
					// Then, using a gathering pass to eval each lightmap.
					solution->curLightPos = lights[i]->Position; // To inform RenderScene function which light is rendering.
					Event<> proc;
					proc.Bind(solution, &LightmapSolution::RenderScene);
					cube->Render(&proc, solution->curLightPos);
					FBO::BindFramebuffer(GL_DRAW_FRAMEBUFFER,0);
					// Gathering Pass: Render each patch into a orthogonal view
					// Execute the shader to compute light distribution from current light.
					// Bind Program
					progPatch->Bind();
					progPatch->SetUniform(L"lightPos", solution->curLightPos);
					progPatch->SetUniform(L"dir", lights[i]->Dir);
					if (lights[i]->DistanceHalf > lights[i]->DistanceFull)
						lights[i]->DistanceFull = lights[i]->DistanceHalf * 2.0f;
					if (lights[i]->DistanceFull < 0.1f)
					{
						progPatch->SetUniform(L"decay", 0);
					}
					else
					{
						progPatch->SetUniform(L"decay", 1);
					}
					progPatch->SetUniform(L"halfDecay", lights[i]->DistanceHalf);
					progPatch->SetUniform(L"fullDecay", lights[i]->DistanceFull);
					if (lights[i]->CutOff > 0.01f)
						progPatch->SetUniform(L"cut", 1);
					else
						progPatch->SetUniform(L"cut", 0);
					progPatch->SetUniform(L"cutOff", lights[i]->CutOff);
					progPatch->SetUniform(L"color", lights[i]->Color*lights[i]->Power);
					BindFragDataLoc();
					MultiTexture::ActiveTexture(GL_TEXTURE0);
					cube->Bind();
					progPatch->SetUniform(L"visMap", 0);
					glDisable(GL_DEPTH_TEST);

					memset(lightmapVis.Buffer(), 0, sizeof(bool)*lightmapVis.Count());
					int leafID = -1;
					scene->SelectLeaf(solution->curLightPos, leafID);
					if (leafID == -1)
						continue;
					for (int j=0; j<scene->BSP.Count(); j++)
					{
						if (scene->BSP[j].IsLeaf())
						{
							for (int k=0; k<scene->BSP[j].SurfaceCount; k++)
							{
								Surface & surf = scene->BSP[j].Surfaces[k];
								lightmapVis[surf.LightmapID] = true;
							}
						}
					}
	 
					for (int j=0; j<scene->Lightmaps.Count(); j++)
					{
						if (!lightmapVis[j])
							continue;
						LightmapInfo & lm = scene->Lightmaps[j];
						// Create View
						int w = GetLightmapWidth(j);
						int h = GetLightmapHeight(j);

						glPushAttrib(GL_VIEWPORT_BIT);
						glViewport(0,0,w, h);
						glMatrixMode(GL_PROJECTION);
						glPushMatrix();
						glLoadIdentity();
						glOrtho(0, w, h, 0, -1.0, 1.0);
						glMatrixMode(GL_MODELVIEW);
						glPushMatrix();
						glLoadIdentity();
						// Set FBO
						RefPtr<RenderBuffer> rb = new RenderBuffer(GL_DEPTH_COMPONENT, w,h);
						fbo->AttachRenderBuffer(GL_DEPTH_ATTACHMENT, rb.operator ->());
						BindFBO(fbo.operator ->(), j);
						fbo->Bind();
						
						glEnable(GL_BLEND);
						glBlendFunc(GL_ONE, GL_ONE);
						progPatch->SetUniform(L"axisX", scene->Lightmaps[j].AxisX);
						progPatch->SetUniform(L"axisY", scene->Lightmaps[j].AxisY);
						progPatch->SetUniform(L"surfaceNormal", scene->Lightmaps[j].Normal);
						progPatch->SetUniform(L"oriPos", scene->Lightmaps[j].OriPos);
						IDEncode e;
						e.ID = j;
						progPatch->SetUniform(L"surface_id", vec4((float)e.R/255.0f, (float)e.G/255.0f, (float)e.B/255.0f, (float)e.A/255.0f));
						glClearColor(0.0f,0.0f,0.0f,1.0f);
						glClear(GL_DEPTH_BUFFER_BIT);
						if (i==0)
							glClear(GL_COLOR_BUFFER_BIT);
						// Draw Patch
						
						glBegin(GL_QUADS);
						{
							float biasX = 1.0f/(w*2.0f);
							float biasY = 1.0f/(h*2.0f);
							glTexCoord2f(biasX,1.0f-biasY);
							glVertex2i(0,0);
							glTexCoord2f(biasX,biasY);
							glVertex2i(0,h);
							glTexCoord2f(1.0f-biasX,biasY);
							glVertex2i(w, h);
							glTexCoord2f(1.0f-biasX,1.0f-biasY);
							glVertex2i(w, 0);
						}
						glEnd();
						fbo->UnBind();
						glPopMatrix();
						glMatrixMode(GL_PROJECTION);
						glPopMatrix();
						glMatrixMode(GL_MODELVIEW);
						glPopAttrib();
						const GLenum drawBuffers[] = {GL_COLOR_ATTACHMENT0};
						OpenGL::DrawBuffers(1, drawBuffers);
					}
					glEnable(GL_DEPTH_TEST);
				}
			}
		};


		
		class BumpedLightmapDirectIlluminationGenerator : public LightmapDirectIlluminationGenerator
		{
		private:
			void CreateProgram()
			{
				progPatch = new Program(L"Shaders/LightmapGen.gxprog");
			}
			void BindFragDataLoc()
			{
				progPatch->BindFragDataLocation(L"fragResult0", 0);
				progPatch->BindFragDataLocation(L"fragResult1", 1);
				progPatch->BindFragDataLocation(L"fragResult2", 2);
			}
			void BindFBO(FrameBufferObject * fbo, int j)
			{
				fbo->AttachTexture(GL_COLOR_ATTACHMENT0, lightmaps[0][j]->GetHandle());
				fbo->AttachTexture(GL_COLOR_ATTACHMENT1, lightmaps[1][j]->GetHandle());
				fbo->AttachTexture(GL_COLOR_ATTACHMENT2, lightmaps[2][j]->GetHandle());
				fbo->Bind();
				const GLenum drawBuffers[] = {GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2};
				OpenGL::DrawBuffers(3, drawBuffers);
			}
			int GetLightmapCount()
			{
				return lightmaps[0].Count();
			}
			int GetLightmapWidth(int id)
			{
				return lightmaps[0][id]->GetWidth();
			}
			int GetLightmapHeight(int id)
			{
				return lightmaps[0][id]->GetHeight();
			}
		public:
			Array<RefPtr<Lightmap>> * lightmaps;
			BumpedLightmapDirectIlluminationGenerator(LightmapSolution * s)
				:LightmapDirectIlluminationGenerator(s)
			{}
		};

		class NonBumpedLightmapDirectIlluminationGenerator : public LightmapDirectIlluminationGenerator
		{
		private:
			void CreateProgram()
			{
				progPatch = new Program(L"Shaders/LightmapGenNonBumped.gxprog");
			}
			void BindFragDataLoc()
			{
				progPatch->BindFragDataLocation(L"fragResult", 0);
			}
			void BindFBO(FrameBufferObject * fbo, int j)
			{
				fbo->AttachTexture(GL_COLOR_ATTACHMENT0, lightmaps[j]->GetHandle());
				const GLenum drawBuffers[] = {GL_COLOR_ATTACHMENT0};
				OpenGL::DrawBuffers(1, drawBuffers);
			}
			int GetLightmapCount()
			{
				return lightmaps.Count();
			}
			int GetLightmapWidth(int id)
			{
				return lightmaps[id]->GetWidth();
			}
			int GetLightmapHeight(int id)
			{
				return lightmaps[id]->GetHeight();
			}
		public:
			Array<RefPtr<Lightmap>> & lightmaps;
			NonBumpedLightmapDirectIlluminationGenerator(LightmapSolution * s, Array<RefPtr<Lightmap>> & lm)
				: LightmapDirectIlluminationGenerator(s), lightmaps(lm)
			{}
		};

		void LightmapSolution::GenerateDirectIllumination(Array<RefPtr<Lightmap>> *lightmaps)
		{
			progLight = new Program(L"Shaders/LightmapGen_Light.gxprog");
			BumpedLightmapDirectIlluminationGenerator gen(this);
			gen.lightmaps = lightmaps;
			gen.GenerateIllum();
		}

		void LightmapSolution::GenerateDirectIlluminationNonBump(Array<RefPtr<Lightmap>> & lm)
		{
			progLight = new Program(L"Shaders/LightmapGen_Light.gxprog");
			NonBumpedLightmapDirectIlluminationGenerator gen(this, lm);
			gen.GenerateIllum();
		}

		struct MapPosition
		{
			int PackID;
			int MapID;
			int X,Y;
		};

		struct LightmapPack
		{
			Array<MapPosition> maps;
			int width, height;
			LightmapPack()
			{
				width = height = 1024;
			}
		};

		struct LightmapSortWrapper
		{
			Lightmap * map;
			int id;
			bool operator > (const LightmapSortWrapper & w)
			{
				return map->GetArea()>w.map->GetArea();
			}
			bool operator < (const LightmapSortWrapper & w)
			{
				return map->GetArea()<w.map->GetArea();
			}
			bool operator == (const LightmapSortWrapper & w)
			{
				return map->GetArea()==w.map->GetArea();
			}
		};

		typedef Array<LightmapSortWrapper> LightmapSet;

		const int LightmapMargin = 2;
		const int DoubleLightmapMargin = LightmapMargin * 2;

		bool ArrangeLightmap(int packID, LightmapPack & pack, 
			int ox, int oy, int w, int h, 
			LightmapSet & set, Array<MapPosition> & packInfo)
		{
			int i;
			for (i=set.Count()-1; i>=0; i--)
			{
				if (set[i].map->GetWidth() <= w-DoubleLightmapMargin &&
					set[i].map->GetHeight() <= h-DoubleLightmapMargin)
				{
					break;
				}
			}
			if (i == -1)
				return false;
			LightmapSortWrapper lm = set[i];
			set.Delete(i,1);
			MapPosition pos;
			pos.MapID = lm.id;
			pos.X = ox+LightmapMargin;
			pos.Y = oy+LightmapMargin;
			pos.PackID = packID;
			int wInc = lm.map->GetWidth()+DoubleLightmapMargin;
			int hInc = lm.map->GetHeight()+DoubleLightmapMargin;
			pack.maps.Add(pos);
			packInfo[pos.MapID] = pos;
			int areaA = (w-wInc)*h;
			int areaB = (h-hInc)*w;
			if (areaA > areaB)
			{
				ArrangeLightmap(packID, pack, ox+wInc, oy, w-wInc, h, set, packInfo);
				ArrangeLightmap(packID, pack, ox, oy+hInc, wInc, h-hInc, set, packInfo);
			}
			else
			{
				ArrangeLightmap(packID, pack, ox, oy+hInc, w, h-hInc, set, packInfo);
				ArrangeLightmap(packID, pack, ox+wInc, oy, w-wInc, hInc, set, packInfo);
			}
			return true;
		}

		void LightmapSolution::Merge()
		{
			Array<MapPosition> PackInfo; // PackInfo[i] is the package id of original lightmap[i]
			PackInfo.SetSize(lightmaps[0].Count());
			// Remove complete blank lightmaps
			Array<float> lmBuf;
			for (int i=0; i<lightmaps[0].Count(); i++)
			{
				lmBuf.Clear();
				
				bool blank = true;
				for (int z=0; z<3; z++)
				{
					lightmaps[z][i]->CopyImage(lmBuf);
					for (int j=0; j<lmBuf.Count(); j++)
					{
						if (lmBuf[j] > 0.0f)
						{
							blank = false;
							break;
						}
					}
					if (!blank)
						break;
				}
				if (blank)
				{
					PackInfo[i].MapID = i;
					PackInfo[i].PackID = 0;
					PackInfo[i].X = 0;
					PackInfo[i].Y = 0;
					lightmaps[0][i]->SetSize(0,0);
				}
			}
			
			
			// Sort Lightmaps by area
			Array<LightmapSortWrapper> maps;
			maps.SetCapacity(scene->Lightmaps.Count());
			for (int i=0; i<scene->Lightmaps.Count(); i++)
			{
				LightmapSortWrapper w;
				if (lightmaps[0][i]->GetArea() > 0)
				{
					w.map = lightmaps[0][i].operator ->();
					w.id = i;
					maps.Add(w);
				}
			}
			maps.Sort();
			Array<RefPtr<LightmapPack>> packs;
			// Pack lightmaps to packages
			while (maps.Count())
			{
				RefPtr<LightmapPack> pack = new LightmapPack();
				bool packed = ArrangeLightmap(packs.Count(), *pack, 0,0, pack->width,pack->height, maps, PackInfo);
				if (!packed)
					break;
				packs.Add(pack);
			}
			// Create packages for residual large lightmaps that cannot be accommodated into packages.
			for (int i=0; i<maps.Count(); i++)
			{
				RefPtr<LightmapPack> pack = new LightmapPack();
				pack->width = maps[i].map->GetWidth()+DoubleLightmapMargin;
				pack->height = maps[i].map->GetHeight()+DoubleLightmapMargin;
				
				MapPosition m;
				m.MapID = maps[i].id;
				m.PackID = packs.Count();
				m.X = m.Y = LightmapMargin;
				PackInfo[maps[i].id] = m;
				pack->maps.Add(m);
				packs.Add(pack);
			}
			if (packs.Count() == 0)
				throw LightmapErrorException();
			// Update Lightmap Coordinates
			for (int i=0; i<scene->BSP.Count(); i++)
			{
				if (scene->BSP[i].IsLeaf())
				{
					for (int j=0; j<scene->BSP[i].SurfaceCount; j++)
					{
						Surface * surf = &scene->BSP[i].Surfaces[j];
						int x0 = PackInfo[surf->LightmapID].X;
						int y0 = PackInfo[surf->LightmapID].Y;
						int w0 = lightmaps[0][surf->LightmapID]->GetWidth();
						int h0 = lightmaps[0][surf->LightmapID]->GetHeight();
						int packID = PackInfo[surf->LightmapID].PackID;
						float biasX = 1.0f/(packs[packID]->width*2.0f);
						float biasY = 1.0f/(packs[packID]->height*2.0f);
						for (int k=0; k<surf->VertexCount; k++)
						{
							Vertex & vert = scene->Vertices[surf->Vertices[k]];
							float u0 = vert.LightmapCoord.x;
							float v0 = vert.LightmapCoord.y;
							vert.LightmapCoord.x = (x0+u0*(w0-1))/(packs[packID]->width);
							vert.LightmapCoord.y = (y0+v0*(h0-1))/(packs[packID]->height);
							vert.LightmapCoord.x += 0.5f/(packs[packID]->width);
							vert.LightmapCoord.y += 0.5f/(packs[packID]->height);
						}
						surf->LightmapID = packID;
					}
				}
			}
			ChangeStatus(L"Post processing...", L"Flooding pixels...");
			// Copy TexImages
			Array<RefPtr<Lightmap>> nlightmaps[3];
			Array<float> pix;
			int progVal = 0;
			for (int z=0; z<3; z++)
			{
				for (int i=0; i<packs.Count(); i++)
				{
					RefPtr<Lightmap> m = new Lightmap(packs[i]->width, packs[i]->height);
					pix.SetSize(packs[i]->width * packs[i]->height * 3);
					for (int j=0; j<pix.Count(); j++)
					{
						pix[j] = -1.0f;
					}
					for (int j=0; j<packs[i]->maps.Count(); j++)
					{
						int mid = packs[i]->maps[j].MapID;
						Array<float> buffer;
						lightmaps[z][mid]->CopyImage(buffer);
						for (int k=0; k<lightmaps[z][mid]->GetHeight(); k++)
						{
							float * src = buffer.Buffer() + lightmaps[z][mid]->GetWidth()*3*k;
							float * dest = pix.Buffer() + packs[i]->width * (packs[i]->maps[j].Y+k) * 3
								+ packs[i]->maps[j].X*3;
							memcpy(dest, src, lightmaps[z][mid]->GetWidth()*3*sizeof(float));
						}
					}
					m->SetImage(pix);
					FloodPixels(m.operator ->());
					nlightmaps[z].Add(m);
					progVal ++;
					ChangeProgress(progVal, packs.Count()*3);
				}
				lightmaps[z].Clear();
				lightmaps[z].Add(nlightmaps[z]);
			}
		}
	}
}